// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include <QApplication>
#include <QString>
#include <QFileDialog>
#include <QFileInfo>

#include "fqterm.h"
#include "fqterm_config.h"
#include "fqterm_param.h"
#include "fqterm_telnet.h"
#include "fqterm_zmodem.h"
#include "fqterm_filedialog.h"

#ifdef FQTERM_ZMODEM_DEBUG
#include <sys/time.h>
#endif

namespace FQTerm {

static const uchar zeros[4] =	 { 0, 0, 0, 0 };
static const char hexChars[] = "0123456789abcdef";

static const uchar AckStr[1] =  { ACK };
static const uchar NakStr[1] =  { NAK };
static const uchar CanStr[2] =  { CAN, CAN };
static const uchar eotstr[1] =  { EOT };


/*
 *  Crc calculation stuff
 */

/* crctab calculated by Mark G. Mendel, Network Systems Corporation */
unsigned short crctab[256] =  {
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129,
    0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273,
    0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a,
    0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6,
    0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf,
    0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695,
    0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
    0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc,
    0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4,
    0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf,
    0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5,
    0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a,
    0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32,
    0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59,
    0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
    0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9,
    0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290,
    0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8,
    0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481,
    0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f,
    0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676,
    0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a,
    0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
    0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75,
    0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f,
    0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64,
    0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c,
    0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93,
    0x3eb2, 0x0ed1, 0x1ef0
};

/*
 * updcrc macro derived from article Copyright (C) 1986 Stephen Satchell.
 *  NOTE: First argument must be in range 0 to 255.
 *        Second argument is referenced twice.
 *
 * Programmers may incorporate any or all code into their programs,
 * giving proper credit within the source. Publication of the
 * source routines is permitted so long as proper credit is given
 * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg,
 * Omen Technology.
 */

#define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp)

/*
 * Copyright (C) 1986 Gary S. Brown.  You may use this program, or
 * code or tables extracted from it, as desired without restriction.
 */

/* First, the polynomial itself and its table of feedback terms.  The  */
/* polynomial is                                                       */
/* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */
/* Note that we take it "backwards" and put the highest-order term in  */
/* the lowest-order bit.  The X^32 term is "implied"; the LSB is the   */
/* X^31 term, etc.  The X^0 term (usually shown as "+1") results in    */
/* the MSB being 1.                                                    */

/* Note that the usual hardware shift register implementation, which   */
/* is what we're using (we're merely optimizing it by doing eight-bit  */
/* chunks at a time) shifts bits into the lowest-order term.  In our   */
/* implementation, that means shifting towards the right.  Why do we   */
/* do it this way?  Because the calculated CRC must be transmitted in  */
/* order from highest-order term to lowest-order term.  UARTs transmit */
/* characters in order from LSB to MSB.  By storing the CRC this way,  */
/* we hand it to the UART in the order low-byte to high-byte; the UART */
/* sends each low-bit to hight-bit; and the result is transmission bit */
/* by bit from highest- to lowest-order term without requiring any bit */
/* shuffling on our part.  Reception works similarly.                  */

/* The feedback terms table consists of 256, 32-bit entries.  Notes:   */
/*                                                                     */
/*     The table can be generated at runtime if desired; code to do so */
/*     is shown later.  It might not be obvious, but the feedback      */
/*     terms simply represent the results of eight shift/xor opera-    */
/*     tions for all combinations of data and CRC register values.     */
/*                                                                     */
/*     The values must be right-shifted by eight bits by the "updcrc"  */
/*     logic; the shift must be unsigned (bring in zeroes).  On some   */
/*     hardware you could probably optimize the shift in assembler by  */
/*     using byte-swap instructions.                                   */

unsigned long cr3tab[] =  {/* CRC polynomial 0xedb88320 */
    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};

#define UPDC32(b, c) (cr3tab[((int)c ^ b) & 0xff] ^ ((c >> 8) & 0x00FFFFFF))

#define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp)

StateTable FQTermZmodem::RStartOps[] =  {
    {ZSINIT,    "RStartOps\0",&FQTermZmodem::GotSinit,    0, 1, RSinitWait} ,          /* SINIT, wait for attn str */
    {ZFILE,     "RStartOps\0",&FQTermZmodem::GotFile,     0, 0, RFileName} ,           /* FILE, wait for filename */
    {ZRQINIT,   "RStartOps\0",&FQTermZmodem::SendRinit,   0, 1, RStart} ,              /* sender confused, resend */
    {ZFIN,      "RStartOps\0",&FQTermZmodem::GotFin,      1, 0, RFinish} ,             /* sender shutting down */
    {ZNAK,      "RStartOps\0",&FQTermZmodem::SendRinit,   1, 0, RStart} ,              /* RINIT was bad, resend */
    {ZFREECNT,  "RStartOps\0",&FQTermZmodem::GotFreecnt,  0, 0, RStart} ,              /* sender wants free space */
    {ZCOMMAND,  "RStartOps\0",&FQTermZmodem::GotCommand,  0, 0, CommandData} ,         /* sender wants command */
    {ZSTDERR,   "RStartOps\0",&FQTermZmodem::GotStderr,   0, 0, StderrData} ,          /* sender wants to send msg */
    {ZRINIT,    "RStartOps\0",&FQTermZmodem::ZmodemTInit,       1, 1, TStart} ,
    {ZEOF,      "RStartOps\0",&FQTermZmodem::GotEofDoNothing,   1, 1, RStart} ,          //
    {99,        "RStartOps\0",&FQTermZmodem::ZPF,               0, 0, RStart}              	 /* anything else is an error */
};

StateTable FQTermZmodem::RSinitWaitOps[] ={
    { 99, "RSinitWaitOps\0",&FQTermZmodem::ZPF, 0, 0, RSinitWait	},	/* waiting for data */
};

StateTable FQTermZmodem::RFileNameOps[] ={
    {99, "RFileNameOps\0",&FQTermZmodem::ZPF, 0, 0, RFileName},      /* waiting for file name */
};

StateTable FQTermZmodem::RCrcOps[] ={
    { ZCRC,     "RCrcOps\0",&FQTermZmodem::GotFileCrc,      0, 0, RFile },  /* waiting for CRC */
    { ZNAK,     "RCrcOps\0",&FQTermZmodem::ResendCrcReq,    0, 0, RCrc },   /* sender sent it */
    { ZRQINIT,  "RCrcOps\0",&FQTermZmodem::SendRinit,       1, 1, RStart }, /* ZCRC was bad, resend */
    { ZFIN,     "RCrcOps\0",&FQTermZmodem::GotFin,          1, 1, RFinish },/* sender confused, restart */
    { 99,       "RCrcOps\0",&FQTermZmodem::ZPF,             0, 0, RCrc },   /* sender signing off */
};


StateTable FQTermZmodem::RFileOps[] ={
    { ZDATA,    "RFileOps\0",&FQTermZmodem::GotData,     0, 0, RData},       /* waiting for ZDATA */
    { ZNAK,     "RFileOps\0",&FQTermZmodem::ResendRpos,  0, 0, RFile },      /* got it */
    { ZEOF,     "RFileOps\0",&FQTermZmodem::GotEof,      0, 0, RFinish },      // ZRPOS was bad, resend
    { ZRQINIT,  "RFileOps\0",&FQTermZmodem::SendRinit,   1, 1, RStart },     /* end of file */
    { ZFILE,    "RFileOps\0",&FQTermZmodem::ResendRpos,  0, 0, RFile },      /* sender confused, restart */
    { ZFIN,     "RFileOps\0",&FQTermZmodem::GotFin,      1, 1, RFinish  },   /* ZRPOS was bad, resend */
    { 99,       "RFileOps\0",&FQTermZmodem::ZPF,         0, 0, RFile },      /* sender signing off */
};

/* waiting for data, but a packet could possibly arrive due
 * to error recovery or something
 */
StateTable FQTermZmodem::RDataOps[] =	{
    { ZRQINIT,  "RDataOps\0",&FQTermZmodem::SendRinit,   1, 1, RStart},
    { ZFILE,    "RDataOps\0",&FQTermZmodem::GotFile,     0, 1, RFileName },  /* sender confused, restart */
    { ZNAK,     "RDataOps\0",&FQTermZmodem::ResendRpos,  1, 1, RFile },      /* start a new file (??) */
    //{ ZFIN,     "RDataOps\0",&FQTermZmodem::GotFin,      1, 1, RStart },    /* ZRPOS was bad, resend */
    { ZDATA,    "RDataOps\0",&FQTermZmodem::GotData,     0, 1, RData },      /* sender signing off */
    { ZEOF,     "RDataOps\0",&FQTermZmodem::GotEof,      1, 1, RFinish },     /*RStart file data follows */

    { 99,       "RDataOps\0",&FQTermZmodem::ZPF,         0, 0, RData },      /* end of file */
};

/* here if we've sent ZFERR or ZABORT.  Waiting for ZFIN */
StateTable FQTermZmodem::RFinishOps[] ={
    { ZRQINIT,  "RFinishOps\0",&FQTermZmodem::SendRinit,   1, 1, RStart } ,
    { ZFILE,    "RFinishOps\0",&FQTermZmodem::GotFile,     1, 1, RFileName },  /* sender confused, restart */
    { ZNAK,     "RFinishOps\0",&FQTermZmodem::GotFin,      1, 1, RFinish },    /* start a new file */
    { ZFIN,     "RFinishOps\0",&FQTermZmodem::GotFin,      1, 1, RStart },     /* resend ZFIN */
    { ZEOF,     "RStartOps\0",&FQTermZmodem::GotEofDoNothing,   1, 1, RFinish} ,          //
    { 99,       "RFinishOps\0",&FQTermZmodem::ZPF,         0, 0, RFinish },    /* sender signing off */
};


/* sent ZRQINIT, waiting for response */
StateTable FQTermZmodem::TStartOps[] =  {
    { ZRINIT,       "TStartOps\0",&FQTermZmodem::GotRinit,        1, 1, TStart },
    { ZCHALLENGE,   "TStartOps\0",&FQTermZmodem::AnswerChallenge, 1, 0, TStart },
    { ZABORT,       "TStartOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { ZFERR,        "TStartOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { ZNAK,         "TStartOps\0",&FQTermZmodem::Ignore,          0, 0, TStart },
    { ZCOMMAND,     "TStartOps\0",&FQTermZmodem::GotCommand,      0, 0, CommandData },
    { ZSTDERR,      "TStartOps\0",&FQTermZmodem::GotStderr,       0, 0, StderrData },
    { ZEOF,         "RStartOps\0",&FQTermZmodem::GotEofDoNothing, 1, 1, RStart} ,          //
    { 99,           "TStartOps\0",&FQTermZmodem::ZPF,             0, 0, RStart },
    };

/* sent ZSINIT, waiting for response */
StateTable FQTermZmodem::TInitOps[] =  {
    { ZACK,         "TInitOps\0",&FQTermZmodem::RetDone,         1, 0, TInit },
    { ZNAK,         "TInitOps\0",&FQTermZmodem::SendZSInit,      1, 0, TInit},
    { ZRINIT,       "TInitOps\0",&FQTermZmodem::GotRinit,        1, 1, TInit },
    { ZCHALLENGE,   "TInitOps\0",&FQTermZmodem::AnswerChallenge, 1, 0, TInit },      /* redundant, but who cares */
    { ZABORT,       "TInitOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { ZFERR,        "TInitOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish	},
    { ZCOMMAND,     "TInitOps\0",&FQTermZmodem::GotCommand,      0, 0, CommandData },
    { ZSTDERR,      "TInitOps\0",&FQTermZmodem::GotStderr,       0, 0, StderrData },
    { 99,           "TInitOps\0",&FQTermZmodem::ZPF,             0, 0, TInit },
    };

/* sent ZFILE, waiting for response */
StateTable FQTermZmodem::FileWaitOps[] ={
    { ZRPOS,        "FileWaitOps\0",&FQTermZmodem::SendFileData,    1, 1, Sending	},
    { ZSKIP,        "FileWaitOps\0",&FQTermZmodem::SkipFile,        1, 0, TStart },
    { ZCRC,         "FileWaitOps\0",&FQTermZmodem::SendFileCrc,     1, 0, FileWait },
    { ZNAK,         "FileWaitOps\0",&FQTermZmodem::sendFilename,    1, 0, FileWait	},
    { ZRINIT,       "FileWaitOps\0",&FQTermZmodem::sendFilename,    1, 1, FileWait },
    { ZABORT,       "FileWaitOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },    /* rcvr confused, retry file */
    { ZFERR,        "FileWaitOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish	},
    { ZCHALLENGE,   "FileWaitOps\0",&FQTermZmodem::AnswerChallenge, 1, 0, FileWait },
    { ZCOMMAND,     "FileWaitOps\0",&FQTermZmodem::GotCommand,      0, 0, CommandData },
    { ZSTDERR,      "FileWaitOps\0",&FQTermZmodem::GotStderr,       0, 0, StderrData },
    { ZACK,         "FileWaitOps\0",&FQTermZmodem::SendFileData,    1, 0, Sending },
    { 99,           "FileWaitOps\0",&FQTermZmodem::ZPF,             0, 0, FileWait },   // receiver always sends ZACK back
};

/* sent file CRC, waiting for response */
StateTable FQTermZmodem::CrcWaitOps[] =  {
    { ZRPOS,        "CrcWaitOps\0",&FQTermZmodem::SendFileData,    1, 0, Sending	},
    { ZSKIP,        "CrcWaitOps\0",&FQTermZmodem::SkipFile,        1, 0, FileWait },
    { ZNAK,         "CrcWaitOps\0",&FQTermZmodem::SendFileCrc,     1, 0, CrcWait },
    { ZRINIT,       "CrcWaitOps\0",&FQTermZmodem::sendFilename,    1, 1, FileWait },
    { ZABORT,       "CrcWaitOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },    /* rcvr confused, retry file */
    { ZFERR,        "CrcWaitOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { ZCRC,         "CrcWaitOps\0",&FQTermZmodem::SendFileCrc,     0, 0, CrcWait },
    { ZCHALLENGE,   "CrcWaitOps\0",&FQTermZmodem::AnswerChallenge, 0, 0, CrcWait },
    { ZCOMMAND,     "CrcWaitOps\0",&FQTermZmodem::GotCommand,      0, 0, CommandData },
    { ZSTDERR,      "CrcWaitOps\0",&FQTermZmodem::GotStderr,       0, 0, StderrData },
    { 99,           "CrcWaitOps\0",&FQTermZmodem::ZPF,             0, 0, CrcWait },
    };

/* sending data, interruptable */
StateTable FQTermZmodem::SendingOps[] =  {
    { ZACK,         "SendingOps\0",&FQTermZmodem::GotSendAck,      0, 0, Sending },
    { ZRPOS,        "SendingOps\0",&FQTermZmodem::GotSendPos,      1, 1, Sending  },
    { ZSKIP,        "SendingOps\0",&FQTermZmodem::SkipFile,        1, 1, FileWait },
    { ZNAK,         "SendingOps\0",&FQTermZmodem::GotSendNak,      1, 1, Sending },
    { ZRINIT,       "SendingOps\0",&FQTermZmodem::sendFilename,    1, 1, FileWait },
    { ZABORT,       "SendingOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish }, /* rcvr confused, retry file */
    { ZFERR,        "SendingOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { 99,           "SendingOps\0",&FQTermZmodem::ZPF,             0, 0, SendWait },
    };

/* sent data, need to send EOF */
StateTable FQTermZmodem::SendDoneOps[] = {
    { ZACK,         "SendDoneOps\0",&FQTermZmodem::GotSendDoneAck,  0, 0, SendWait },
    { ZRPOS,        "SendDoneOps\0",&FQTermZmodem::GotSendPos,      1, 1, Sending },
    { ZSKIP,        "SendDoneOps\0",&FQTermZmodem::SkipFile,        1, 1, FileWait },
    { ZNAK,         "SendDoneOps\0",&FQTermZmodem::GotSendNak,      1, 1, Sending },
    { ZRINIT,       "SendDoneOps\0",&FQTermZmodem::sendFilename,    1, 1, FileWait },
    { ZABORT,       "SendDoneOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },/* rcvr confused, retry file */
    { ZFERR,        "SendDoneOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { 99,           "SendDoneOps\0",&FQTermZmodem::ZPF,             0, 0, SendWait },
    };

/* sending data, waiting for ACK */
StateTable FQTermZmodem::SendWaitOps[] ={
    { ZACK,         "SendWaitOps\0",&FQTermZmodem::GotSendWaitAck,  0, 0, Sending },
    { ZRPOS,        "SendWaitOps\0",&FQTermZmodem::GotSendPos,      0, 0, SendWait },
    { ZSKIP,        "SendWaitOps\0",&FQTermZmodem::SkipFile,        1, 1, FileWait },
    { ZNAK,         "SendWaitOps\0",&FQTermZmodem::GotSendNak,      0, 0, Sending },
    { ZRINIT,       "SendWaitOps\0",&FQTermZmodem::sendFilename,    1, 1, FileWait },
    { ZABORT,       "SendWaitOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish }, /* rcvr confused, retry file */
    { ZFERR,        "SendWaitOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { 99,           "SendWaitOps\0",&FQTermZmodem::ZPF,             0, 0, SendWait },
    };

/* sent ZEOF, waiting for new RINIT */
StateTable FQTermZmodem::SendEofOps[] =  {
    { ZRINIT,   "SendEofOps\0",&FQTermZmodem::SkipFile,        1, 0, TFinish },
    { ZACK,     "SendEofOps\0",&FQTermZmodem::Ignore,          0, 0, SendEof },/* successful completion */
    { ZRPOS,    "SendEofOps\0",&FQTermZmodem::GotSendPos,      1, 1, SendWait },/* probably ACK from last packet */
    { ZSKIP,    "SendEofOps\0",&FQTermZmodem::SkipFile,        1, 1, TStart },
    { ZNAK,     "SendEofOps\0",&FQTermZmodem::ResendEof,       1, 0, SendEof },
    { ZRINIT,   "SendEofOps\0",&FQTermZmodem::sendFilename,    1, 1, FileWait },
    { ZABORT,   "SendEofOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish }, /* rcvr confused, retry file */
    { ZFERR,    "SendEofOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { 99,       "SendEofOps\0",&FQTermZmodem::ZPF,             0, 0, SendEof },
    };

StateTable FQTermZmodem::TFinishOps[] =  {
    { ZFIN,     "TFinishOps\0",&FQTermZmodem::OverAndOut,      1, 1, Done },
    { ZNAK,     "TFinishOps\0",&FQTermZmodem::ZmodemTFinish,   1, 1, TFinish },
    { ZRINIT,   "TFinishOps\0",&FQTermZmodem::ZmodemTFinish,   1, 1, TFinish },
    { ZABORT,   "TFinishOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { ZFERR,    "TFinishOps\0",&FQTermZmodem::GotAbort,        1, 1, TFinish },
    { 99,       "TFinishOps\0",&FQTermZmodem::ZPF,             0, 0, TFinish },
    };

StateTable FQTermZmodem::CommandDataOps[] =  {
    { 99, "CommandDataOps\0",&FQTermZmodem::ZPF, 0, 0, CommandData	},
    };

StateTable FQTermZmodem::CommandWaitOps[] =  {	{ 99, "CommandWaitOps\0",&FQTermZmodem::ZPF, 0, 0, CommandWait	} };

StateTable FQTermZmodem::StderrDataOps[] =  { { 99, "StderrDataOps\0",&FQTermZmodem::ZPF, 0, 0, StderrData } };

StateTable FQTermZmodem::DoneOps[] = { { 99, "DoneOps\0",&FQTermZmodem::ZPF, 0, 0, Done },};

/// 状态机总表
StateTable *FQTermZmodem::tables[] ={
    FQTermZmodem::RStartOps,
    FQTermZmodem::RSinitWaitOps,
    FQTermZmodem::RFileNameOps,
    FQTermZmodem::RCrcOps,
    FQTermZmodem::RFileOps,
    FQTermZmodem::RDataOps,
    FQTermZmodem::RDataOps,  /* RDataErr is the same as RData */
    FQTermZmodem::RFinishOps,
    FQTermZmodem::TStartOps,
    FQTermZmodem::TInitOps,
    FQTermZmodem::FileWaitOps,
    FQTermZmodem::CrcWaitOps,
    FQTermZmodem::SendingOps,
    FQTermZmodem::SendWaitOps,
    FQTermZmodem::SendDoneOps,
    FQTermZmodem::SendEofOps,
    FQTermZmodem::TFinishOps,
    FQTermZmodem::CommandDataOps,
    FQTermZmodem::CommandWaitOps,
    FQTermZmodem::StderrDataOps,
    FQTermZmodem::DoneOps,
};

const char *FQTermZmodem::hdrnames[] =  {
    "ZRQINIT", "ZRINIT", "ZSINIT", "ZACK", "ZFILE", "ZSKIP", "ZNAK", "ZABORT",
    "ZFIN", "ZRPOS", "ZDATA", "ZEOF", "ZFERR", "ZCRC", "ZCHALLENGE", "ZCOMPL",
    "ZCAN", "ZFREECNT", "ZCOMMAND", "ZSTDERR",
};


FQTermZmodem::FQTermZmodem(FQTermConfig *config, FQTermTelnet *netinterface, int type, int serverEncoding):
    __p_file_dialog( nullptr ),
    __m_cooled( true ),
    __m_cool_timer( nullptr )

{
    __m_cool_timer.setSingleShot( true );
    connect(&__m_cool_timer , &QTimer::timeout , this , &FQTermZmodem::__on_cooled );
    //now set network interface Telnet
    connectionType = type;
    serverEncodingID = serverEncoding;

    switch (connectionType) {
    case 0:
    case 1:
    case 2:
        telnet_ = netinterface;
        break;
    default:
        FQ_TRACE("zmodem", 0) << "connection type unknown! Expect segmentation fault!";
        telnet_ =  netinterface;
        break;
    }

    config_ = config;

    sending = false;

    transferstate = notransfer;

    zmodemTimer = new QTimer(this);
    FQ_VERIFY(connect(zmodemTimer, SIGNAL(timeout()), this, SLOT(ZmodemTimeout())));

#ifdef FQTERM_ZMODEM_DEBUG
    zmodemlogfile = fopen("zmodem.log", "w+");
    fprintf(zmodemlogfile, "%s", "\n================================\n");
    fclose(zmodemlogfile);
#endif
    //init struct INFO
    info.zrinitflags = CANFDX | CANOVIO | CANBRK | CANFC32;
    info.zsinitflags = 0;
    info.attn = NULL;
    info.bufsize = 0; /* full streaming */
    info.buffer = NULL;

    zerrno = 0;
    lastPullByte = 0;

    ZmodemReset(&info);
    // other init function not complete
}

FQTermZmodem::~FQTermZmodem()
{
    if (info.buffer != NULL){
        free(info.buffer);
    }
}

void FQTermZmodem :: __on_cooled()
{
    __m_cooled = true;
}

void FQTermZmodem :: __on_selected_files(  bool sw , const QStringList& files )
{
    int err;

    if( sw == false ){
        zmodemCancel();
        ZmodemReset(&info);
        disconnect( __p_file_dialog );
        delete __p_file_dialog;
        __p_file_dialog = nullptr;
        __m_cooled = true;
        return;
    }
    strFileList = files;
    if (strFileList.count() != 0) {
        QStringList::Iterator itFile = strFileList.begin();
        QFileInfo fi(*itFile);
    }
    disconnect( __p_file_dialog );
    delete __p_file_dialog;

    this->transferstate = transferstop;

    // optional: send "rz\r" to remote end
    if(DoInitRZ) {
        if (ZXmitStr((uchar*)"rz\r", 3, &info)) { return; }
    }

    if( ZXmitHdr(ZRQINIT, ZHEX, zeros, &info )) { return; }

    info.timeout = 60;
}

int FQTermZmodem::ZmodemTInit( ZModem *info )
{
    int err = 0, i = 0;

    info->state         = TStart;
    info->Protocol      = ZMODEM;
    info->crc32         = 0;
    info->packetCount   = 0;
    info->errCount      = 0;
    info->escCtrl       = 0;
    info->escHibit      = 0;
    info->atSign        = 0;
    info->escape        = 0;
    info->InputState    = Idle;
    info->canCount      = 0;
    info->chrCount      = 0;
    info->windowCount   = 0;
    info->filename      = nullptr;
    info->bufsize       = 0;
    info->interrupt     = 0;
    info->waitflag      = 0;

    info->packetsize    = 8096;
    info->windowsize    = 0;
    //we won't be receiving much data, pick a reasonable buffer size (largest packet will do)
    i = info->packetsize *2;

    if (info->buffer != NULL) {
        free(info->buffer);
        info->buffer = NULL;
    }
    //since in the constructor function buffer is malloc

    if (i < 1024) {
        i = 1024;
    }
    info->buffer = (uchar*)malloc(i);

    ZIFlush(info);
    try{
        if( __p_file_dialog == nullptr && __m_cooled.load() == true ){
            __m_cooled = false;
            __p_file_dialog = new FQTermFileDialog( config_ );
            connect( __p_file_dialog , &FQTermFileDialog::selectedFiles , this , &FQTermZmodem::__on_selected_files );
            __p_file_dialog->getOpenNames("选择文件上传", "");
        }
    }catch(std::bad_alloc& e ){
        return -10000;
    }

    zmodemlog("ZmodemTInit[%s]: sent ZRQINIT\n", sname(info));

    return 0;
}


int FQTermZmodem::ZmodemTFile(char *file, char *rfile, uint f0, uint f1, uint f2, uint f3, int filesRem, int bytesRem, ZModem *info)
{
    if (file == NULL || (info->file = fopen(file, "rb")) == NULL) {
        return ZmErrCantOpen;
    }

    info->fileEof = 0;
    info->filename = file;
    if (rfile != NULL) {
        info->rfilename = rfile;
    } else {
        info->rfilename = strdup("noname");
    }
    info->filesRem = filesRem;
    info->bytesRem = bytesRem;
    info->fileFlags[3] = f0;
    info->fileFlags[2] = f1;
    info->fileFlags[1] = f2;
    info->fileFlags[0] = f3;
    info->offset = info->lastOffset = 0;
    info->len = info->date = info->fileType = info->mode = 0;
    if (info->filename != NULL) {
        struct stat buf;
        if (stat(info->filename, &buf) == 0) {
            info->len = buf.st_size;
            info->date = buf.st_mtime;
            info->fileType = 0;
            info->mode = (buf.st_mode &0777) | 0100000;
        }
    }

    if (info->Protocol == XMODEM) {
        return YSendData(info);
    }

    if (info->Protocol == YMODEM) {
        return YSendFilename(info);
    }

    info->state = FileWait;
    ZStatus(FileBegin, info->bytesRem, info->rfilename);
    zmodemlog("ZmodemTFile[%s]: send ZFILE(%s)\n", sname(info), info->rfilename);
    return sendFilename(info);
}


int FQTermZmodem::ZmodemTFinish(ZModem *info)
{
    int i;
    if (info->Protocol == XMODEM) {
        return ZmDone;
    }

    if (info->Protocol == YMODEM) {
        return YSendFin(info);
    }

    info->state = TFinish;
    if (info->buffer != NULL) {
        free(info->buffer);
        info->buffer = NULL;
    }
    zmodemlog("ZmodemTFinish[%s]: send ZFIN\n", sname(info));
    i = ZXmitHdr(ZFIN, ZHEX, zeros, info);

    return i;
}

int FQTermZmodem::ZmodemAbort(ZModem *info) {
    uchar canistr[] =  {
        CAN, CAN, CAN, CAN, CAN, CAN, CAN, CAN, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0
    };
    info->state = Done;
    ZIFlush(info);
    ZOFlush(info);

    transferstate = transferstop; // transfer complete
    ZmodemReset(info); //Transfer complete, zmodem return to receive state

    zmodemlog("ZmodemAbort[%s]: send CAN\n", sname(info));
    return ZXmitStr(canistr, sizeof(canistr), info);
}


int FQTermZmodem::ZmodemRInit(ZModem *info)
{
    info->packetCount   = 0;
    info->offset        = 0;
    info->errCount      = 0;
    info->escCtrl       = 0;
    info->escHibit      = 0;
    info->atSign        = 0;
    info->escape        = 0;
    info->InputState    = Idle;
    info->canCount      = 0;
    info->chrCount      = 0;
    info->filename      = nullptr;
    info->interrupt     = 0;
    info->waitflag      = 0;
    info->attn          = nullptr;
    info->file          = nullptr;

    if (info->buffer != nullptr ) {
        free( info->buffer );
        info->buffer = nullptr;
    }

    info->buffer = (uchar*)malloc(1024);

    info->state = RStart;
    info->timeoutCount = 0;

    ZIFlush(info);

    /* Don't send ZRINIT right away, there might be a ZRQINIT in
	 * the input buffer.  Instead, set timeout to zero and return.
	 * This will allow ZmodemRcv() to check the input stream first.
	 * If nothing found, a ZRINIT will be sent immediately.
     */
    info->timeout = 0;

    zmodemlog("ZmodemRInit[%s]: flush input, new state = RStart\n", sname(info));

    return 0;
}

int FQTermZmodem::ZmodemRcv(uchar *str, int len, ZModem *info, int &consumed_bytes)
{
    uchar c;
    int err;

    zmodemlog("zmodemRcv called");

    info->rcvlen = len;

    while (--info->rcvlen >= 0) {
        c =  *str++;

        if (c == CAN) {
            if (++info->canCount >= 5 ) {
                ZStatus(RmtCancel, 0, NULL);
                consumed_bytes = len - info->rcvlen - 1;
                return ZmErrCancel;
            }
        } else {
            info->canCount = 0;
        }

        if (info->InputState == Ysend) {
            if ((err = YsendChar(c, info))) {
                consumed_bytes = len - info->rcvlen - 1;
                return err;
            }
        } else if (info->InputState == Yrcv) {
            if ((err = YrcvChar(c, info))) {
                consumed_bytes = len - info->rcvlen - 1;
                return err;
            }
        } else if (c != XON && c != XOFF) { /* now look at what we have */
            switch (info->InputState) {
            case Idle:
                if ((err = IdleChar(c, info))) {
                    consumed_bytes = len - info->rcvlen - 1;
                    return err;
                }
                break;
            case Inhdr:
                if ((err = HdrChar(c, info))) {
                    consumed_bytes = len - info->rcvlen - 1;
                    return err;
                }
                break;
            case Indata:
                if ((err = DataChar(c, info))) {
                    consumed_bytes = len - info->rcvlen - 1;
                    return err;
                }
                break;
            case Finish:
                if ((err = FinishChar(c, info))) {
                    consumed_bytes = len - info->rcvlen - 1;
                    return err;
                }
                break;
            default:
                break;
            }
        }
    }

    consumed_bytes = len - info->rcvlen - 1;
    return 0;
}

int FQTermZmodem::ZmodemTimeout( /*ZModem *info*/) {
    /* timed out while waiting for input */
    ++info.timeoutCount;

    zmodemlog("timeout %d [%s]\n", info.timeoutCount, sname(&info));

    switch (info.state) {/* receive */
    case RStart:
        /* waiting for INIT frame from other end */
        if (info.timeoutCount > 4) {
            return YmodemRInit(&info);
        }

    case RSinitWait:
    case RFileName:
        if (info.timeout > 0) {
            ZStatus(SndTimeout, info.timeoutCount, NULL);
        }
        if (info.timeoutCount > 10) {
            return ZmErrRcvTo;
        }
        info.state = RStart;
        return SendRinit(&info);

    case RCrc:
    case RFile:
    case RData:
        ZStatus(SndTimeout, info.timeoutCount, NULL);
        if (info.timeoutCount > 2) {
            info.timeoutCount = 0;
            info.state = RStart;
            return SendRinit(&info);
        }
        return info.state == RCrc ? ResendCrcReq(&info): ResendRpos(&info);

    case RFinish:
        ZStatus(SndTimeout, info.timeoutCount, NULL);
        return ZmDone;

    case YRStart:
    case YRDataWait:
    case YRData:
    case YREOF:
        return YrcvTimeout(&info);

        /* transmit */
    case TStart:
        /* waiting for INIT frame from other end */
    case TInit:
        /* sent INIT, waiting for ZACK */
    case FileWait:
        /* sent file header, waiting for ZRPOS */
    case CrcWait:
        /* sent file crc, waiting for ZRPOS */
    case SendWait:
        /* waiting for ZACK */
    case SendEof:
        /* sent EOF, waiting for ZACK */
    case TFinish:
        /* sent ZFIN, waiting for ZFIN */
    case YTStart:
    case YTFile:
    case YTDataWait:
    case YTData:
    case YTEOF:
    case YTFin:
        ZStatus(RcvTimeout, 0, NULL);
        return ZmErrRcvTo;

    case Sending:
        /* sending data subpackets, ready for int */
        return SendMoreFileData(&info);

        /* general */
    case CommandData:
        /* waiting for command data */
    case StderrData:
        /* waiting for stderr data */
        return ZmErrSndTo;
    case CommandWait:
        /* waiting for command to execute */
        return ZmErrCmdTo;
    case Done:
        return ZmDone;
    default:
        return 0;
    }
}



int FQTermZmodem::ZmodemAttention(ZModem *info)
{
    /* attention received from remote end */
    if (info->state == Sending) {
        ZOFlush(info);
        info->interrupt = 1;
    }
    return 0;
}

int FQTermZmodem::YmodemTInit(ZModem *info) {
    info->state = YTStart;
    info->Protocol = YMODEM;
    info->errCount = 0;
    info->InputState = Ysend;
    info->canCount = info->chrCount = 0;
    info->windowCount = 0;
    info->filename = NULL;

    if (info->packetsize != 1024) {
        info->packetsize = 128;
    }

    info->buffer = (uchar*)malloc(1024);

    ZIFlush(info);
    ZFlowControl(0, info);

    info->timeout = 60;

    return 0;
}


int FQTermZmodem::XmodemTInit(ZModem *info) {
    (void)YmodemTInit(info);
    info->Protocol = XMODEM;
    return 0;
}


int FQTermZmodem::YmodemRInit(ZModem *info) {
    info->errCount = 0;
    info->InputState = Yrcv;
    info->canCount = info->chrCount = 0;
    info->noiseCount = 0;
    info->filename = NULL;
    info->file = NULL;

    if (info->buffer == NULL) {
        info->buffer = (uchar*)malloc(1024);
    }

    info->state = YRStart;
    info->packetCount = -1;
    info->timeoutCount = 0;
    info->timeout = 10;
    info->offset = 0;

    ZIFlush(info);

    return ZXmitStr((uchar*)"C", 1, info);
}


int FQTermZmodem::XmodemRInit(ZModem *info) {
#if 0
	int err;

	state = Start;
	protocol = prot;
	ymodem = prot == Ymodem || prot == YmodemG;

	if (ymodem) {
		strcpy(xmDefPath, file);
	} else {
		strcpy(xmFilename, file);
	}

	eotCount = errorCount = errorCount2 = 0;

	if (err = XmodemRStart()) {
		return err;
	}

	state = Init;
	packetId = ymodem ? 255 : 0;
	packetCount = 0;

	pktHdrLen = protocol == Xmodem ? 3 : 4;
#endif
    return 0;
}


ulong FQTermZmodem::FileCrc(char *name) {
    ulong crc;
    FILE *ifile = fopen(name, "rb");
    int i;

    if (ifile == NULL)
    /* shouldn't happen, since we did access(2) */ {
        return 0;
    }

    crc = 0xffffffff;

    while ((i = fgetc(ifile)) != EOF) {
        crc = UPDC32(i, crc);
    }

    fclose(ifile);
    return ~crc;
}



const char *FQTermZmodem::sname(ZModem *info) {
    return sname2(info->state);
}

const char *FQTermZmodem::sname2(ZMState state) {
    const char *names[] =  {
        "RStart", "RSinitWait", "RFileName", "RCrc", "RFile", "RData", "RDataErr",
        "RFinish", "TStart", "TInit", "FileWait", "CrcWait", "Sending", "SendWait",
        "SendDone", "SendEof", "TFinish", "CommandData", "CommandWait", "StderrData",
        "Done", "YTStart", "YTFile", "YTDataWait", "YTData", "YTEOF", "YTFin",
        "YRStart", "YRDataWait", "YRData", "YREOF"
    };

    return names[(int)state];

}


int FQTermZmodem::ZXmitChr(uchar c, ZModem *info) {
    // to be completed
    return 0;
}

int FQTermZmodem::ZXmitStr(const uchar *str, int len, ZModem *info) {
    //to be completed
    telnet_->write((const char*)str, (uint)len);
    return 0;
}

void FQTermZmodem::ZIFlush(ZModem *info) {
    //to be completed
}

void FQTermZmodem::ZOFlush(ZModem *info) {
    //to be completed
}

int FQTermZmodem::ZAttn(ZModem *info) {
    //to be completed
    return 0;
}

void FQTermZmodem::ZStatus(int type, int value, const char *status) {
    emit ZmodemState(type, value, status);
    switch (type) {
    case RcvByteCount:
        FQ_TRACE("zmodem", 5) << value << " bytes received.";
        break;
    case SndByteCount:
        FQ_TRACE("zmodem", 5) << value << " bytes sent.";
        break;
    case RcvTimeout: /* receiver did not respond, aborting */
        FQ_TRACE("zmodem", 0) << "Zmodem time out!";
        break;
    case SndTimeout: /* value is # of consecutive send timeouts */
        FQ_TRACE("zmodem", 0) << "Zmodem time out after trying " << value << " times";
        break;
    case RmtCancel:	/* remote end has cancelled */
        FQ_TRACE("zmodem", 1) << "Zmodem canceled by remote peer";
        break;
    case ProtocolErr:/* protocol error has occurred, val=hdr */
        FQ_TRACE("zmodem", 0) << "Unhandled header " << value << " at state " << status;
        break;
    case RemoteMessage:	/* message from remGote end */
        FQ_TRACE("zmodem", 0) << "Message from remote peer: " << status;
        break;
    case DataErr:/* data error, val=error count */
        FQ_TRACE("zmodem", 0) << "Data errors " << value;
        break;
    case FileErr: /* error writing file, val=errno */
        FQ_TRACE("zmodem", 0) << "Falied to write file.";
        break;
    case FileBegin:
        /* file transfer begins, str=name */
        FQ_TRACE("zmodem", 0) << "Starting file: " << status;
        break;
    case FileEnd:/* file transfer ends, str=name */
        FQ_TRACE("zmodem", 0) << "Finishing file: " << status;
        __m_cool_timer.start( 500 );
        __p_file_dialog = nullptr;
        break;
    case FileSkip:	/* file being skipped, str=name */
        FQ_TRACE("zmodem", 0) << "Skipping file: " << status;
        break;
    }
}

FILE *FQTermZmodem::ZOpenFile(char *name, ulong crc, ZModem *info) {
    //to be complete
    FILE *rval;
    int apnd = 0;
    QString strText;
    strText = encoding2unicode(name, serverEncodingID);
    QString str = FQTermPref::getInstance()->zmodemDir_ + strText;

    QString zpath = QFileInfo(FQTermPref::getInstance()->zmodemDir_).absoluteFilePath();
    QString path = QFileInfo(str).absoluteFilePath();

    lastDownloadedFilename_ = path;

    if (path.startsWith(zpath)) {
        // Ensure that file is only saved under zmodemDir_.
        // TODO: lazy, should use bbs2unicode
        rval = fopen(str.toLocal8Bit(), apnd ? "ab" : "wb");
    } else {
        rval = NULL;
    }

    if (rval == NULL) {
        perror(name);
    }

    return rval;
}

int FQTermZmodem::ZXmitHdr(int type, int format, const uchar data[4], ZModem *info) {
    if (format == ZBIN && info->crc32) {
        format = ZBIN32;
    }

    switch (format) {
    case ZHEX:
        return ZXmitHdrHex(type, data, info);

    case ZBIN:
        return ZXmitHdrBin(type, data, info);

    case ZBIN32:
        return ZXmitHdrBin32(type, data, info);

    default:
        return 0;
    }
}


int FQTermZmodem::ZXmitHdrHex(int type, const uchar data[4], ZModem *info) {
    uchar buffer[128];
    uchar *ptr = buffer;
    uint crc;
    int i;

    zmodemlog("sending  %s: %2.2x %2.2x %2.2x %2.2x = %lx\n", hdrnames[type],
              data[0], data[1], data[2], data[3], ZDec4(data));

    *ptr++ = ZPAD;
    *ptr++ = ZPAD;
    *ptr++ = ZDLE;
    *ptr++ = ZHEX;

    ptr = putHex(ptr, type);
    crc = updcrc(type, 0);
    for (i = 4; --i >= 0; ++data) {
        ptr = putHex(ptr,  *data);
        crc = updcrc(*data, crc);
    }
    crc = updcrc(0, crc);
    crc = updcrc(0, crc);
    ptr = putHex(ptr, (crc >> 8) &0xff);
    ptr = putHex(ptr, crc &0xff);
    *ptr++ = '\r';
    *ptr++ = '\n';
    if (type != ZACK && type != ZFIN) {
        *ptr++ = XON;
    }

    return ZXmitStr(buffer, ptr - buffer, info);
}


int FQTermZmodem::ZXmitHdrBin(int type, const uchar data[4], ZModem *info) {
    uchar buffer[128];
    uchar *ptr = buffer;
    uint crc;
    int len;

    zmodemlog("sending  %s: %2.2x %2.2x %2.2x %2.2x = %lx\n", hdrnames[type], data[0], data[1], data[2], data[3], ZDec4(data));

    *ptr++ = ZPAD;
    *ptr++ = ZDLE;
    *ptr++ = ZBIN;

    ptr = putZdle(ptr, type, info);
    crc = updcrc(type, 0);
    for (len = 4; --len >= 0; ++data) {
        ptr = putZdle(ptr,  *data, info);
        crc = updcrc(*data, crc);
    }
    crc = updcrc(0, crc);
    crc = updcrc(0, crc);
    ptr = putZdle(ptr, (crc >> 8) &0xff, info);
    ptr = putZdle(ptr, crc &0xff, info);

    len = ptr - buffer;
    return ZXmitStr(buffer, len, info);
}

int FQTermZmodem::ZXmitHdrBin32(int type, const uchar data[4], ZModem *info) {
    uchar buffer[128];
    uchar *ptr = buffer;
    ulong crc;
    int len;

    zmodemlog("sending  %s: %2.2x %2.2x %2.2x %2.2x = %lx\n", hdrnames[type],
              data[0], data[1], data[2], data[3], ZDec4(data));

    *ptr++ = ZPAD;
    *ptr++ = ZDLE;
    *ptr++ = ZBIN32;
    ptr = putZdle(ptr, type, info);
    crc = UPDC32(type, 0xffffffffL);
    for (len = 4; --len >= 0; ++data) {
        ptr = putZdle(ptr,  *data, info);
        crc = UPDC32(*data, crc);
    }
    crc = ~crc;
    for (len = 4; --len >= 0; crc >>= 8) {
        ptr = putZdle(ptr, crc &0xff, info);
    }

    len = ptr - buffer;
    return ZXmitStr(buffer, len, info);
}


uchar *FQTermZmodem::putZdle(uchar *ptr, uchar c, ZModem *info) {
    uchar c2 = c &0177;

    if (c == ZDLE || c2 == 020 || c2 == 021 || c2 == 023 || c2 == 0177 || (c2 ==
                                                                               015 && connectionType == 0 /*&& info->atSign*/) ||
#ifdef COMMENT
        c2 == 035 || (c2 == '~' && info->lastCR) ||
#endif /* COMMENT */
        c2 == 035 || (c2 < 040 && info->escCtrl)) {
        *ptr++ = ZDLE;
        if (c == 0177) {
            *ptr = ZRUB0;
        } else if (c == 0377) {
            *ptr = ZRUB1;
        } else {
            *ptr = c ^ 0100;
        }
    } else {
        *ptr = c;
    }

    info->atSign = c2 == '@';
    info->lastCR = c2 == '\r';

    return ++ptr;
}


uchar *FQTermZmodem::ZEnc4(ulong n) {
    static uchar buf[4];
    buf[0] = n &0xff;
    n >>= 8;
    buf[1] = n &0xff;
    n >>= 8;
    buf[2] = n &0xff;
    n >>= 8;
    buf[3] = n &0xff;
    return buf;
}

ulong FQTermZmodem::ZDec4(const uchar buf[4])
{
    return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
}




int FQTermZmodem::YrcvChar(char c, ZModem *info) {
    int err;

    if (info->canCount >= 2) {
        ZStatus(RmtCancel, 0, NULL);
        return ZmErrCancel;
    }

    switch (info->state) {
    case YREOF:
        if (c == EOT) {
            ZCloseFile(info);
            info->file = NULL;
            ZStatus(FileEnd, 0, info->filename);
            if (info->filename != NULL) {
                free(info->filename);
            }
            if ((err = acceptPacket(info)) != 0) {
                return err;
            }
            info->packetCount = -1;
            info->offset = 0;
            info->state = YRStart;
            return ZXmitStr((uchar*)"C", 1, info);
        }
    case YRStart:
    case YRDataWait:
        switch (c) {
        case SOH:
        case STX:
            info->pktLen = c == SOH ? (128+4): (1024+4);
            info->state = YRData;
            info->chrCount = 0;
            info->timeout = 1;
            info->noiseCount = 0;
            info->crc = 0;
            break;

        case EOT:
            /* ignore first EOT to protect against false eot */
            info->state = YREOF;
            return rejectPacket(info);

        default:
            if (++info->noiseCount > 135) {
                return ZXmitStr(NakStr, 1, info);
            }
            break;
        }
        break;

    case YRData:
        info->buffer[info->chrCount++] = c;
        if (info->chrCount >= info->pktLen) {
            return ProcessPacket(info);
        }
        break;

    default:
        break;
    }

    return 0;
}

int FQTermZmodem::YrcvTimeout(ZModem *info) {
    switch (info->state) {
    case YRStart:
        if (info->timeoutCount >= 10) {
            (void)ZXmitStr(CanStr, 2, info);
            return ZmErrInitTo;
        }
        return ZXmitStr((uchar*)"C", 1, info);

    case YRDataWait:
    case YREOF:
    case YRData:
        if (info->timeoutCount >= 10) {
            (void)ZXmitStr(CanStr, 2, info);
            return ZmErrRcvTo;
        }
        return ZXmitStr(NakStr, 1, info);
    default:
        return 0;
    }
}


void FQTermZmodem::ZIdleStr(uchar *buffer, int len, ZModem *info) {
    //to be completed
}


int FQTermZmodem::FinishChar(char c, ZModem *info) {
    if (c == 'O') {
        if (++info->chrCount >= 2) {
            return ZmDone;
        }
    } else {
        info->chrCount = 0;
    }
    return 0;
}



int FQTermZmodem::DataChar(uchar c, ZModem *info)
{
    if (c == ZDLE) {
        info->escape = 1;
        return 0;
    }

    if (info->escape) {
        info->escape = 0;
        switch (c) {
        case ZCRCE:
        case ZCRCG:
        case ZCRCQ:
        case ZCRCW:
            info->PacketType = c;
            info->crcCount = (info->DataType == ZBIN32) ? 4 : 2;
            if (info->DataType == ZBIN) {
                info->crc = updcrc(c, info->crc);
            } else {
                info->crc = UPDC32(c, info->crc);
            }
            return 0;
        case ZRUB0:
            c = 0177;
            break;
        case ZRUB1:
            c = 0377;
            break;
        default:
            c ^= 0100;
            break;
        }
    }
    if (connectionType == 0) {
        if (lastPullByte == 0x0d && c == 0x00) {
            lastPullByte = 0;
            return 0;
        } else if (lastPullByte == 0xff && c == 0xff) {
            lastPullByte = 0;
            return 0;
        }
    }
    lastPullByte = c;

    switch (info->DataType) { /* TODO: are hex data packets ever used? */
    case ZBIN:
        info->crc = updcrc(c, info->crc);
        if (info->crcCount == 0) {
            info->buffer[info->chrCount++] = c;
        } else if (--info->crcCount == 0) {
            return ZDataReceived(info, (info->crc &0xffff) == 0);
        }
        break;


    case ZBIN32:
        info->crc = UPDC32(c, info->crc);
        if (info->crcCount == 0) {
            info->buffer[info->chrCount++] = c;
        } else if (--info->crcCount == 0) {
            return ZDataReceived(info, info->crc == 0xdebb20e3);
        }
        break;
    }
    return 0;
}


int FQTermZmodem::HdrChar(uchar c, ZModem *info) {
    int i;
    int crc = 0;

    if (c == ZDLE) {
        info->escape = 1;
        return 0;
    }

    if (info->escape) {
        info->escape = 0;
        switch (c) {
        case ZRUB0: c = 0177; break;
        case ZRUB1: c = 0377; break;
        default: c ^= 0100;	break;
        }
    }

    if (info->chrCount == 0) { /* waiting for format */
        switch (c) {
        case ZHEX:
        case ZBIN:
        case ZBIN32:
            info->DataType = c;
            info->chrCount = 1;
            info->crc = (info->DataType != ZBIN32) ? 0 : 0xffffffffL;
            memset(info->hdrData, 0, sizeof(info->hdrData));
            break;
        default:
            info->InputState = Idle;
            info->chrCount = 0;
            return ZXmitHdrHex(ZNAK, zeros, info);
        }
        return 0;
    }


    switch (info->DataType) {
        /* hex header is 14 hex digits, cr, lf.  Optional xon is ignored */
    case ZHEX:
        if (info->chrCount <= 14 && !isxdigit(c)) {
            info->InputState = Idle;
            info->chrCount = 0;
            return ZXmitHdrHex(ZNAK, zeros, info);
        }

        if (info->chrCount <= 14) {
            i = (info->chrCount - 1) / 2;
            info->hdrData[i] = rcvHex(info->hdrData[i], c);
        }

        if (info->chrCount == 16) {
            crc = 0;
            for (i = 0; i < 7; ++i) {
                crc = updcrc(info->hdrData[i], crc);
            }
            info->InputState = Idle;
            info->chrCount = 0;
            if ((crc &0xffff) != 0) {
                return ZXmitHdrHex(ZNAK, zeros, info);
            } else {
                return ZProtocol(info);
            }
        }
        else {
            ++info->chrCount;
        }
        break;
    case ZBIN:
        /* binary header is type, 4 bytes data, 2 bytes CRC */
        info->hdrData[info->chrCount - 1] = c;
        info->crc = updcrc(c, info->crc);
        if (++info->chrCount > 7) {
            info->InputState = Idle;
            info->chrCount = 0;
            if ((crc &0xffff) != 0) {
                return ZXmitHdrHex(ZNAK, zeros, info);
            } else {
                return ZProtocol(info);
            }
        }
        break;
    case ZBIN32: /* binary32 header is type, 4 bytes data, 4 bytes CRC */
        info->hdrData[info->chrCount - 1] = c;
        info->crc = UPDC32(c, info->crc);
        if (++info->chrCount > 9) {
            info->InputState = Idle;
            info->chrCount = 0;
            if (info->crc != 0xdebb20e3 ){ /* see note below */
                FQ_TRACE( "info" , 0 ) << "info->crc != 0xdebb20e3 REPLAY NACK";
                return ZXmitHdrHex(ZNAK, zeros, info);
            } else {
                return ZProtocol(info);
            }
        }
        break;
    }
    return 0;
}




int FQTermZmodem::IdleChar(uchar c, ZModem *info) {
    if (info->chrCount == 0) {
        if (c == ZPAD) {
            ++info->chrCount;
        } else if (info->state == Sending && ++info->noiseCount > MaxNoise) {
            info->waitflag = 1;
        } else if (info->state == TStart && (c == 'C' || c == 'G' || c == NAK)) {
            /* switch to ymodem */
            info->state = YTStart;
            info->InputState = Ysend;
            info->Protocol = YMODEM;
            return YsendChar(c, info);
        } else {
            ZIdleStr(&c, 1, info);
        }
    }

    else {
        switch (c) {
        case ZPAD:
            ++info->chrCount;
            break;
        case ZDLE:
            info->InputState = Inhdr;
            info->chrCount = 0;
            break;
        default:
            while (--info->chrCount >= 0) {
                ZIdleStr((uchar*)"*", 1, info);
            }
            info->chrCount = 0;
            break;
        }
    }
    return 0;
}



int FQTermZmodem::YsendChar(char c, ZModem *info) {
    int err;

    if (info->canCount >= 2) {
        ZStatus(RmtCancel, 0, NULL);
        return ZmErrCancel;
    }

    switch (info->state) {
    case YTStart:
        /* wait for 'G', 'C' or NAK */
        switch (c) {
        case 'G':
            /* streaming YModem */
        case 'C':
            /* CRC YModem */
        case NAK:
            /* checksum YModem */
            info->PacketType = c;
            return ZmDone;
        default:
            return 0;
        }

    case YTFile:
        /* sent filename, waiting for ACK or NAK */
        switch (c) {
        case NAK:
            /* resend */
        case 'C':
        case 'G':
            ZStatus(DataErr, ++info->errCount, NULL);
            return YSendFilename(info);
        case ACK:
            info->state = YTDataWait;
        default:
            return 0;
        }

    case YTDataWait:
        /* sent filename, waiting for G,C or NAK */
        switch (c) {
        case NAK:
        case 'C':
        case 'G':
            info->chrCount = 0;
            if (info->PacketType == 'G') {
                /* send it all at once */
                while (info->state == YTData) {
                    if ((err = YSendData(info))) {
                        return err;
                    }
                }
                return 0;
            } else {
                return YSendData(info);
            }
            break;
        default:
            return 0;
        }

    case YTData:
        /* sent data, waiting for ACK or NAK */
        switch (c) {
        case 'C':
        case 'G':
            /* protocol failure, resend filename */
            if (info->Protocol == YMODEM) {
                ZStatus(DataErr, ++info->errCount, NULL);
                info->state = YTFile;
                rewind(info->file);
                return YSendFilename(info);
            }
            /* else XModem, treat it like a NAK */
        case NAK:
            ZStatus(DataErr, ++info->errCount, NULL);
            return YXmitData(info->buffer + info->bufp, info->ylen, info);
        case ACK:
            info->offset += info->ylen;
            info->bufp += info->ylen;
            info->chrCount -= info->ylen;
            ZStatus(SndByteCount, info->offset, NULL);
            return YSendData(info);
        default:
            return 0;
        }

    case YTEOF:
        /* sent EOF, waiting for ACK or NAK */
        switch (c) {
        case NAK:
            return ZXmitStr(eotstr, 1, info);
        case ACK:
            info->state = info->Protocol == YMODEM ? YTStart : Done;
            return ZmDone;
        default:
            return 0;
        }

    case YTFin:
        /* sent Fin, waiting for ACK or NAK */
        switch (c) {
        case NAK:
            return YSendFin(info);
        case ACK:
            return ZmDone;
        default:
            return 0;
        }
    default:
        return 0;
    }
}

int FQTermZmodem::ZProtocol(ZModem *info)
{
    StateTable *table;

    zmodemlog("received %s: %2.2x %2.2x %2.2x %2.2x = %lx\n", hdrnames[info->hdrData[0]], info->hdrData[1], info->hdrData[2],
              info->hdrData[3], info->hdrData[4], ZDec4(info->hdrData + 1));

    /* Flags are sent in F3 F2 F1 F0 order.  Data is sent in P0 P1 P2 P3 */
    info->timeoutCount = 0;
    info->noiseCount = 0;
    if( info->timeout == 0 ){
        info->timeout = 60;
    }
    //zmodemTimer->start(info->timeout*1000);
    // 当前状态
    table = tables[ (int)info->state ];
    // 尝试查找符合数据包条件的状态进行处理
    while ( table->type != 99 && (uchar)table->type != info->hdrData[0]) {
        ++table;
    }

    zmodemlog("  state %s => %s, iflush=%d, oflush=%d, call %x\n", sname(info), sname2(table->newstate), table->IFlush, table->OFlush, table->func);

    info->state = table->newstate;
    // 一下两个处理暂时没有实质内容
    if (table->IFlush) {
        info->rcvlen = 0;
        ZIFlush(info);
    }
    if (table->OFlush) {
        ZOFlush(info);
    }
    // 调用实际的处理方法
    return (this->*(table->func))(info);
}


int FQTermZmodem::ZDataReceived(ZModem *info, int crcGood) {

    switch (info->state) {
    case RSinitWait:
        return GotSinitData(info, crcGood);
    case RFileName:
        return GotFileName(info, crcGood);
    case RData:
        return GotFileData(info, crcGood);
    case CommandData:
        return GotCommandData(info, crcGood);
    case StderrData:
        return GotStderrData(info, crcGood);
    default:
        return ZPF(info);
    }
}


int FQTermZmodem::ZPF(ZModem *info)
{
    info->waitflag = 1; /* pause any in-progress transmission */
    ZStatus(ProtocolErr, info->hdrData[0], sname(info));
    return 0;
}


int FQTermZmodem::Ignore(ZModem *info)
{
    return 0;
}


int FQTermZmodem::AnswerChallenge(ZModem *info)
{
    return ZXmitHdrHex(ZACK, info->hdrData + 1, info);
}


int FQTermZmodem::GotAbort(ZModem *info)
{
    ZStatus(RmtCancel, 0, NULL);
    return ZXmitHdrHex(ZFIN, zeros, info);
}


int FQTermZmodem::GotCancel(ZModem *info)
{
    return ZmErrCancel;
}


int FQTermZmodem::GotCommand(ZModem *info)
{
    uchar rbuf[4];
    /* TODO: add command capability */
    /// EPERM not defined????????
    rbuf[0] = EPERM;
    rbuf[1] = rbuf[2] = rbuf[3] = 0;
    return ZXmitHdrHex(ZCOMPL, rbuf, info);
}

int FQTermZmodem::GotStderr(ZModem *info)
{
    info->InputState = Indata;
    info->chrCount = 0;
    return 0;
}


int FQTermZmodem::RetDone(ZModem *info)
{
    return ZmDone;
}


int FQTermZmodem::GotCommandData(ZModem *info, int crcGood)
{
    /* TODO */
    return 0;
}


int FQTermZmodem::GotStderrData(ZModem *info, int crcGood)
{
    info->buffer[info->chrCount] = '\0';
    ZStatus(RemoteMessage, info->chrCount, (char*)info->buffer);
    return 0;
}

int FQTermZmodem::GotFileName(ZModem *info, int crcGood)
{
    info->InputState = Idle;
    info->chrCount = 0;

    if (!crcGood) {
        zmodemlog("GotFileName[%s]: bad crc, send ZNAK\n", sname(info));
        info->state = RStart;
        return ZXmitHdrHex(ZNAK, zeros, info);
    }

    parseFileName(info, (char*)info->buffer);

    if ((info->f1 &ZMMASK) == ZMCRC) {
        info->state = RCrc;
        return ZXmitHdrHex(ZCRC, zeros, info);
    }

    zmodemlog("GotFileName[%s]: good crc, call requestFile\n", sname(info));
    info->state = RFile;
    return requestFile(info, 0);
}


int FQTermZmodem::ResendCrcReq(ZModem *info)
{
    zmodemlog("ResendCrcReq[%s]: send ZCRC\n", sname(info));
    return ZXmitHdrHex(ZCRC, zeros, info);
}


int FQTermZmodem::GotSinitData(ZModem *info, int crcGood)
{
    info->InputState = Idle;
    info->chrCount = 0;
    info->state = RStart;

    zmodemlog("GotSinitData[%s]: crcGood=%d\n", sname(info), crcGood);

    if (!crcGood) {
        return ZXmitHdrHex(ZNAK, zeros, info);
    }

    if (info->attn != NULL) {
        free(info->attn);
    }
    info->attn = NULL;
    if (info->buffer[0] != '\0') {
        info->attn = strdup((char*)info->buffer);
    }
    return ZXmitHdrHex(ZACK, ZEnc4(SerialNo), info);
}


int FQTermZmodem::ResendRpos(ZModem *info)
{
    zmodemlog("ResendRpos[%s]: send ZRPOS(%ld)\n", sname(info), info->offset);
    return ZXmitHdrHex(ZRPOS, ZEnc4(info->offset), info);
}


int FQTermZmodem::GotFileData(ZModem *info, int crcGood)
{
    /* OK, now what?  Fushing the buffers and executing the
	 * attn sequence has likely chopped off the input stream
	 * mid-packet.  Now we switch to idle mode and treat all
	 * incoming stuff like noise until we get a new valid
	 * packet.
     */
    if (!crcGood) {
        /* oh bugger, an error. */
        zmodemlog("GotFileData[%s]: bad crc, send ZRPOS(%ld), new state = RFile\n",
                  sname(info), info->offset);
        ZStatus(DataErr, ++info->errCount, NULL);
        if (info->errCount > MaxErrs) {
            ZmodemAbort(info);
            return ZmDataErr;
        } else {
            info->state = RFile;
            info->InputState = Idle;
            info->chrCount = 0;
            return fileError(info, ZRPOS, info->offset);
        }
    }

    if (ZWriteFile(info->buffer, info->chrCount, info->file, info)) {	/* RED ALERT!  Could not write the file. */
        ZStatus(FileErr, zerrno, NULL);
        info->state = RFinish;
        info->InputState = Idle;
        info->chrCount = 0;
        return fileError(info, ZFERR, zerrno);
    }

    zmodemlog("GotFileData[%s]: %ld.%d,", sname(info), info->offset,  info->chrCount);
    info->offset += info->chrCount;

    ZStatus(RcvByteCount, info->offset, NULL);

    /* if this was the last data subpacket, leave data mode */
    if (info->PacketType == ZCRCE || info->PacketType == ZCRCW) {
        zmodemlog("  ZCRCE|ZCRCW, new state RFile");
        info->state = RFile;
        info->InputState = Idle;
        info->chrCount = 0;
    } else {
        zmodemlog("  call dataSetup");
        (void)dataSetup(info);
    }

    //if (info->PacketType == ZCRCQ || info->PacketType == ZCRCW) {
    zmodemlog(",  send ZACK\n");

    auto ret = ZXmitHdrHex(ZACK, ZEnc4(info->offset), info);
    //} else {
    zmodemlog("\n");
    //}
    //zmodemCancel();
    return ret;
}

int FQTermZmodem::SendRinit(ZModem *info)
{
    uchar dbuf[4];
#ifdef COMMENT
    if (info->timeoutCount >= 5)
    /* TODO: switch to Ymodem */
#endif /* COMMENT */
    {
        transferstate = transferstart;
    }
    //transfer would be active, it must be set to false when transfer complete or abort
    zmodemlog("SendRinit[%s]: send ZRINIT\n", sname(info));

    info->timeout = ResponseTime;
    dbuf[0] = info->bufsize &0xff;
    dbuf[1] = (info->bufsize >> 8) &0xff;
    dbuf[2] = 0;
    dbuf[3] = info->zrinitflags;
    return ZXmitHdrHex(ZRINIT, dbuf, info);
}

int FQTermZmodem::SendMoreFileData(ZModem *info)
{
    int type;
    int qfull = 0;
    int err;
    int len; /* max # chars to send this packet */
    long pending; /* # of characters sent but not acknowledged */

    /* ZCRCE: CRC next, frame ends, header follows
	 * ZCRCG: CRC next, frame continues nonstop
	 * ZCRCQ: CRC next, send ZACK, frame continues nonstop
	 * ZCRCW: CRC next, send ZACK, frame ends, header follows
	 */

    if (info->interrupt) {
        /* Bugger, receiver sent an interrupt.  Enter a wait state
		 * and see what they want.  Next header *should* be ZRPOS.
         */
        info->state = SendWait;
        info->timeout = 60;
        return 0;
    }

    /* Find out how many bytes we can transfer in the next packet */

    len = info->packetsize;

    pending = info->offset - info->lastOffset;

    if (info->windowsize != 0 && info->windowsize - pending <= len) {
        len = info->windowsize - pending;
        qfull = 1;
    }
    if (info->bufsize != 0 && info->bufsize - pending <= len) {
        len = info->bufsize - pending;
        qfull = 1;
    }

    if (len == 0) {
        /* window still full, keep waiting */
        info->state = SendWait;
        info->timeout = 60;
        return 0;
    }


    /* OK, we can safely transmit 'len' bytes of data.  Start reading
	 * file until buffer is full.
	 */

    len -= 10; /* Pre-deduct 10 bytes for trailing CRC */


    /* find out what kind of packet to send */
    if (info->waitflag) {
        type = ZCRCW;
        info->waitflag = 0;
    }
#ifdef COMMENT
    else if (info->fileEof) {
        type = ZCRCE;
    }
#endif /* COMMENT */
    else if (qfull) {
        type = ZCRCW;
    } else {
        switch (info->Streaming) {
        case Full:
        case Segmented:
            type = ZCRCG;
            break;

        case StrWindow:
            if ((info->windowCount += len) < info->windowsize / 4) {
                type = ZCRCG;
            } else {
                type = ZCRCQ;
                info->windowCount = 0;
            }
            break;

        default:
        case SlidingWindow:
            type = ZCRCQ;
            break;
        }
    }

    {
        int crc32 = info->crc32;
        int c = 0, c2;
        ulong crc;
        uchar *ptr = info->buffer;

        crc = crc32 ? 0xffffffff : 0;

        /* read characters from file and put into buffer until buffer is
		 * full or file is exhausted
		 */


        while (len > 0 && (c = getc(info->file)) != EOF) {
            if (!crc32) {
                crc = updcrc(c, crc);
            } else {
                crc = UPDC32(c, crc);
            }

            /* zmodem protocol requires that CAN(ZDLE), DLE, XON, XOFF and
			 * a CR following '@' be escaped.  In addition, I escape '^]'
			 * to protect telnet, "<CR>~." to protect rlogin, and ESC for good
			 * measure.
             */
            c2 = c &0177;
            if (c == ZDLE || c2 == 020 || c2 == 021 || c2 == 023 || c2 == 0177 || c2 ==
                                                                                      '\r' || c2 == '\n' || c2 == 033 || c2 == 035 || (c2 < 040 && info->escCtrl)) {
                *ptr++ = ZDLE;
                if (c == 0177) {
                    *ptr = ZRUB0;
                } else if (c == 0377) {
                    *ptr = ZRUB1;
                } else {
                    *ptr = c ^ 0100;
                }
                len -= 2;
            } else {
                *ptr = c;
                --len;
            }
            ++ptr;

            ++info->offset;
        }

        /* if we've reached file end, a ZEOF header will follow.  If
		 * there's room in the outgoing buffer for it, end the packet
		 * with ZCRCE and append the ZEOF header.  If there isn't room,
		 * we'll have to do a ZCRCW
         */
        if ((info->fileEof = (c == EOF))) {
            if (qfull || (info->bufsize != 0 && len < 24)) {
                type = ZCRCW;
            } else {
                type = ZCRCE;
            }
        }

        *ptr++ = ZDLE;
        if (!crc32) {
            crc = updcrc(type, crc);
        } else {
            crc = UPDC32(type, crc);
        }
        *ptr++ = type;

        if (!crc32) {
            crc = updcrc(0, crc);
            crc = updcrc(0, crc);
            ptr = putZdle(ptr, (crc >> 8) &0xff, info);
            ptr = putZdle(ptr, crc &0xff, info);
        } else {
            crc = ~crc;
            for (len = 4; --len >= 0; crc >>= 8) {
                ptr = putZdle(ptr, crc &0xff, info);
            }
        }

        len = ptr - info->buffer;
    }

    ZStatus(SndByteCount, info->offset, NULL);

    if ((err = ZXmitStr(info->buffer, len, info))) {
        return err;
    }

#ifdef COMMENT
    if ((err = ZXmitData(ZBIN, len, uchar(type), info->buffer, info))) {
        return err;
    }
#endif /* COMMENT */

    /* finally, do we want to wait after this packet? */

    switch (type) {
    case ZCRCE:
        info->state = SendEof;
        info->timeout = 60;
        return ZXmitHdrHex(ZEOF, ZEnc4(info->offset), info);
    case ZCRCW:
        info->state = info->fileEof ? SendDone : SendWait;
        info->timeout = 60;
        break;
    default:
        info->state = Sending;
        info->timeout = 0;
        break;
    }


#ifdef COMMENT
    if (info->fileEof) {
        /* Yes, file is done, send EOF and wait */
        info->state = SendEof;
        info->timeout = 60;
        return ZXmitHdrHex(ZEOF, ZEnc4(info->offset), info);
    } else if (type == ZCRCW) {
        info->state = SendWait;
        info->timeout = 60;
    } else {
        info->state = Sending;
        info->timeout = 0;
    }
#endif /* COMMENT */
    return 0;
}


uint FQTermZmodem::rcvHex(uint i, char c)
{
    if (c <= '9') {
        c -= '0';
    } else if (c <= 'F') {
        c -= 'A' - 10;
    } else {
        c -= 'a' - 10;
    }
    return (i << 4) + c;
}


int FQTermZmodem::dataSetup(ZModem *info)
{
    info->InputState = Indata;
    info->chrCount = 0;
    info->crcCount = 0;
    info->crc = (info->DataType != ZBIN32) ? 0 : 0xffffffffL;
    return 0;
}

int FQTermZmodem::ZWriteFile(uchar *buffer, int len, FILE *file, ZModem*)
{
    return (int)fwrite(buffer, 1, len, file) == len ? 0 : ZmErrSys;
}

int FQTermZmodem::ZCloseFile(ZModem *info)
{
    //to be completed
    fclose(info->file);
    return 0;
}

void FQTermZmodem::ZFlowControl(int onoff, ZModem *info)
{
    //to be completed
}

int FQTermZmodem::GotSinit(ZModem *info)
{
    zmodemlog("GotSinit[%s]: call dataSetup\n", sname(info));

    info->zsinitflags = info->hdrData[4];
    info->escCtrl     = info->zsinitflags &TESCCTL;
    info->escHibit    = info->zsinitflags &TESC8;
    // 没有实际的内容
    ZFlowControl(1, info);

    return dataSetup(info);
}

int FQTermZmodem::GotFile(ZModem *info)
{
    zmodemlog("GotFile[%s]: call dataSetup\n", sname(info));

    info->errCount = 0;
    info->f0 = info->hdrData[4];
    info->f1 = info->hdrData[3];
    info->f2 = info->hdrData[2];
    info->f3 = info->hdrData[1];

    return dataSetup(info);
}

int FQTermZmodem::GotFin(ZModem *info)
{
    int i;
    zmodemlog("GotFin[%s]: send ZFIN\n", sname(info));
    info->InputState = Finish;

    info->chrCount = 0;
    if (info->filename != NULL) {
        free(info->filename);
    }

    zmodemlog("ZmodemTFinish[%s]: send ZFIN\n", sname(info));

    i = ZXmitHdrHex(ZFIN, zeros, info);

    ZmodemReset(info);
    transferstate = transferstop; // transfer complete
    telnet_->clearState();

    return i;
}


int FQTermZmodem::GotData(ZModem *info)
{
    int err;

    zmodemlog("GotData[%s]:\n", sname(info));

    if (ZDec4(info->hdrData + 1) != info->offset) {
        if (info->attn != NULL && (err = ZAttn(info)) != 0) {
            return err;
        }
        zmodemlog("  bad, send ZRPOS(%ld)\n", info->offset);
        return ZXmitHdrHex(ZRPOS, ZEnc4(info->offset), info);
    }

    /* Let's do it! */
    zmodemlog("  call dataSetup\n");

    return dataSetup(info);
}

int FQTermZmodem::GotEofDoNothing( ZModem * )
{
    return 0;
}

int FQTermZmodem::GotEof(ZModem *info)
{
    zmodemlog("GotEof[%s]: offset=%ld\n", sname(info), info->offset);
    if (ZDec4(info->hdrData + 1) != info->offset) {
        zmodemlog("zdec4(info->hdrdata+1)=%ld\n", ZDec4(info->hdrData + 1));
        zmodemlog("  bad length, state = RFile\n");
        info->state = RFile;
        return 0; /* it was probably spurious */
    }

    /* TODO: if we can't close the file, send a ZFERR */
    ZCloseFile(info);
    info->file = NULL;

    ZStatus(FileEnd, 0, info->filename);

    if (info->filename != NULL) {
        free(info->filename);
        info->filename = NULL;
    }

    return SendRinit(info);
}

int FQTermZmodem::GotFreecnt(ZModem *info)
{
    /* TODO: how do we find free space on system? */
    return ZXmitHdrHex(ZACK, ZEnc4(0xffffffff), info);
}


int FQTermZmodem::GotFileCrc(ZModem *info)
{
    zmodemlog("GotFileCrc[%s]: call requestFile\n", sname(info));
    return requestFile(info, ZDec4(info->hdrData + 1));
}

int FQTermZmodem::requestFile(ZModem *info, ulong crc)
{
    info->file = ZOpenFile((char*)info->buffer, crc, info);

    if (info->file == NULL) {
        zmodemlog("requestFile[%s]: send ZSKIP\n", sname(info));

        info->state = RStart;
        ZStatus(FileSkip, 0, info->filename);
        return ZXmitHdrHex(ZSKIP, zeros, info);
    } else {
        zmodemlog("requestFile[%s]: send ZRPOS(%ld)\n", sname(info), info->offset);
        info->offset = info->f0 == ZCRESUM ? ftell(info->file): 0;
        info->state = RFile;
        ZStatus(FileBegin, info->len, info->filename);
        return ZXmitHdrHex(ZRPOS, ZEnc4(info->offset), info);
    }
}

void FQTermZmodem::parseFileName(ZModem *info, char *fileinfo)
{
    char *ptr;
    int serial = 0;

    info->len = info->mode = info->filesRem = info->bytesRem = info->fileType = 0;
    ptr = fileinfo + strlen(fileinfo) + 1;
    if (info->filename != NULL) {
        free(info->filename);
    }
    info->filename = strdup(fileinfo);
    sscanf(ptr, "%d %lo %o %o %d %d %d", &info->len, &info->date,  &info->mode,
           &serial, &info->filesRem, &info->bytesRem,  &info->fileType);
}



int FQTermZmodem::fileError(ZModem *info, int type, int data)
{
    int err;

    info->InputState = Idle;
    info->chrCount = 0;

    if (info->attn != NULL && (err = ZAttn(info)) != 0) {
        return err;
    }
    return ZXmitHdrHex(type, ZEnc4(data), info);
}


int FQTermZmodem::ProcessPacket(ZModem *info)
{
    int idx = (uchar)info->buffer[0];
    int idxc = (uchar)info->buffer[1];
    int crc0, crc1;
    int err;

    info->state = YRDataWait;

    if (idxc != 255-idx) {
        ZStatus(DataErr, ++info->errCount, NULL);
        return rejectPacket(info);
    }

    if (idx == (info->packetCount % 256))
    /* quietly ignore dup */ {
        return acceptPacket(info);
    }

    if (idx != (info->packetCount + 1) % 256) {
        /* out of sequence */
        (void)ZXmitStr(CanStr, 2, info);
        return ZmErrSequence;
    }

    crc0 = (uchar)info->buffer[info->pktLen - 2] << 8
           | (uchar)info->buffer[info->pktLen - 1];
    crc1 = calcCrc(info->buffer + 2, info->pktLen - 4);
    if (crc0 != crc1) {
        ZStatus(DataErr, ++info->errCount, NULL);
        return rejectPacket(info);
    }

    ++info->packetCount;

    if (info->packetCount == 0) {
        /* packet 0 is filename */
        if (info->buffer[2] == '\0') {
            /* null filename is FIN */
            (void)acceptPacket(info);
            return ZmDone;
        }

        parseFileName(info, (char*)info->buffer + 2);
        info->file = ZOpenFile(info->filename, 0, info);
        if (info->file == NULL) {
            (void)ZXmitStr(CanStr, 2, info);
            return ZmErrCantOpen;
        }
        if ((err = acceptPacket(info)) != 0) {
            return err;
        }
        return ZXmitStr((uchar*)"C", 1, info);
    }


    if (ZWriteFile(info->buffer + 2, info->pktLen - 4, info->file, info)) {
        ZStatus(FileErr, zerrno, NULL);
        (void)ZXmitStr(CanStr, 2, info);
        return ZmErrSys;
    }
    info->offset += info->pktLen - 4;
    ZStatus(RcvByteCount, info->offset, NULL);

    (void)acceptPacket(info);
    return 0;
}

int FQTermZmodem::rejectPacket(ZModem *info)
{
    info->timeout = 10;
    return ZXmitStr(NakStr, 1, info);
}


int FQTermZmodem::acceptPacket(ZModem *info)
{
    info->state = YRDataWait;
    info->timeout = 10;
    return ZXmitStr(AckStr, 1, info);
}


int FQTermZmodem::calcCrc(uchar *str, int len)
{
    int crc = 0;
    while (--len >= 0) {
        crc = updcrc(*str++, crc);
    }
    crc = updcrc(0, crc);
    crc = updcrc(0, crc);
    return crc &0xffff;
}


char *FQTermZmodem::strdup(const char *str)
{
    char *rval;
    int len = strlen(str) + 1;
    rval = (char*)malloc(len);
    strncpy(rval, str , len );

    return rval;
}


int FQTermZmodem::ZXmitData(int format, int len, uchar term, uchar *data, ZModem *info)
{
    uchar *ptr = info->buffer;
    uint crc;

    if (format == ZBIN && info->crc32) {
        format = ZBIN32;
    }

    zmodemlog("ZXmiteData: fmt=%c, len=%d, term=%c\n", format, len, term);

    crc = (format == ZBIN) ? 0 : 0xffffffff;

    while (--len >= 0) {
        if (format == ZBIN) {
            crc = updcrc(*data, crc);
        } else {
            crc = UPDC32(*data, crc);
        }
        ptr = putZdle(ptr,  *data++, info);
    }

    *ptr++ = ZDLE;
    if (format == ZBIN) {
        crc = updcrc(term, crc);
    } else {
        crc = UPDC32(term, crc);
    }
    *ptr++ = term;
    if (format == ZBIN) {
        crc = updcrc(0, crc);
        crc = updcrc(0, crc);
        ptr = putZdle(ptr, (crc >> 8) &0xff, info);
        ptr = putZdle(ptr, crc &0xff, info);
    } else {
        crc = ~crc;
        for (len = 4; --len >= 0; crc >>= 8) {
            ptr = putZdle(ptr, crc &0xff, info);
        }
    }

    return ZXmitStr(info->buffer, ptr - info->buffer, info);
}



int FQTermZmodem::YXmitData(uchar *buffer, int len, ZModem *info)
{
    uchar hdr[3];
    uchar trail[2];
    ulong crc = 0;
    int i, err;

    hdr[0] = len == 1024 ? STX : SOH;
    hdr[1] = info->packetCount;
    hdr[2] = ~hdr[1];
    if ((err = ZXmitStr(hdr, 3, info)) || (err = ZXmitStr(buffer, len, info))) {
        return err;
    }

    if (info->PacketType == NAK) {/* checksum */
        for (i = 0; i < len; ++i) {
            crc += buffer[i];
        }
        trail[0] = crc % 256;
        return ZXmitStr(trail, 1, info);
    } else {
        for (i = 0; i < len; ++i) {
            crc = updcrc(buffer[i], crc);
        }
        crc = updcrc(0, crc);
        crc = updcrc(0, crc);
        trail[0] = crc / 256;
        trail[1] = crc % 256;
        return ZXmitStr(trail, 2, info);
    }
}


int FQTermZmodem::YSendFilename(ZModem *info)
{
    int i, len;
    uchar obuf[1024];
    uchar *ptr = obuf;

    info->state = info->PacketType != 'G' ? YTFile : YTDataWait;
    info->packetCount = 0;
    info->offset = 0;

    i = strlen(info->rfilename);
    memcpy(ptr, info->rfilename, i + 1);
    ptr += i + 1;
    sprintf((char*)ptr, "%d %lo %o 0", info->len, info->date, info->mode);
    ptr += strlen((char*)ptr);
    *ptr++ = '\0';
    /* pad out to 128 bytes or 1024 bytes */
    i = ptr - obuf;
    len = i > 128 ? 1024 : 128;
    for (; i < len; ++i) {
        *ptr++ = '\0';
    }

    return YXmitData(obuf, len, info);
}

int FQTermZmodem::YSendData(ZModem *info)
{
    int i;

    /* are there characters still in the read buffer? */

    if (info->chrCount <= 0) {
        info->bufp = 0;
        info->chrCount = fread(info->buffer, 1, info->packetsize, info->file);
        info->fileEof = feof(info->file);
    }

    if (info->chrCount <= 0) {
        fclose(info->file);
        info->state = YTEOF;
        return ZXmitStr(eotstr, 1, info);
    }

    /* pad out to 128 bytes if needed */
    if (info->chrCount < 128) {
        i = 128-info->chrCount;
        memset(info->buffer + info->bufp + info->chrCount, 0x1a, i);
        info->chrCount = 128;
    }

    info->ylen = info->chrCount >= 1024 ? 1024 : 128;
    ++info->packetCount;

    info->state = YTData;

    return YXmitData(info->buffer + info->bufp, info->ylen, info);
}


int FQTermZmodem::YSendFin(ZModem *info)
{
    uchar obuf[128];

    info->state = YTFin;
    info->packetCount = 0;

    memset(obuf, 0, 128);

    return YXmitData(obuf, 128, info);
}

int FQTermZmodem::sendFilename(ZModem *info)
{
    int err;
    int i;
    uchar obuf[2048];
    uchar *ptr = obuf;

    info->state = FileWait;

    if ((err = ZXmitHdr(ZFILE, ZBIN, info->fileFlags, info))) {
        return err;
    }

    i = strlen(info->rfilename);
    memcpy(ptr, info->rfilename, i + 1);
    ptr += i + 1;
    sprintf((char*)ptr, "%d %lo %o 0 %d %d 0", info->len, info->date, info->mode,
            info->filesRem, info->bytesRem);
    ptr += strlen((char*)ptr);
    *ptr++ = '\0';

    return ZXmitData(ZBIN, ptr - obuf, ZCRCW, obuf, info);
}

int FQTermZmodem::GotRinit(ZModem *info)
{
    if (strFileList.count() == 0) {
        return ZmodemTFinish(info);
    }


    info->bufsize = info->hdrData[1] + info->hdrData[2] *256;
    info->zrinitflags = info->hdrData[4] + info->hdrData[3] *256;
    info->crc32 = info->zrinitflags &CANFC32;
    info->escCtrl = info->zrinitflags &ESCCTL;
    info->escHibit = info->zrinitflags &ESC8;

    /* Full streaming: If receiver can overlap I/O, and if
	 * the sender can sample the reverse channel without hanging,
	 * and the receiver has not specified a buffer size, then we
	 * can simply blast away with ZCRCG packets.  If the receiver
	 * detects an error, it sends an attn sequence and a new ZRPOS
	 * header to restart the file where the error occurred.
	 *
	 * [note that zmodem8.doc does not define how much noise is
	 * required to trigger a ZCRCW packet.  We arbitrarily choose
	 * 64 bytes]
	 *
	 * If 'windowsize' is nonzero, and the receiver can do full
	 * duplex, ZCRCQ packets are sent instead of ZCRCG, to keep track
	 * of the number of characters in the queue.  If the queue fills
	 * up, we pause and wait for a ZACK.
	 *
	 *
	 * Full Streaming with Reverse Interrupt:  If sender cannot
	 * sample the input stream, then we define an Attn sequence
	 * that will be used to interrupt transmission.
	 *
	 *
	 * Full Streaming with Sliding Window:  If sender cannot
	 * sample input stream or respond to Attn signal, we send
	 * several ZCRCQ packets until we're sure the receiver must
	 * have sent back at least one ZACK.  Then we stop sending and
	 * read that ZACK.  Then we send one more packet and so on.
	 *
	 *
	 * Segmented Streaming:  If receiver cannot overlap I/O or can't do
	 * full duplex and has specified a maximum receive buffer size,
	 * whenever the buffer size is reached, we send a ZCRCW packet.
	 *
	 * TODO: what if receiver can't overlap, but hasn't set a buffer
	 * size?
	 *
	 * ZCRCE: CRC next, frame ends, header follows
	 * ZCRCG: CRC next, frame continues nonstop
	 * ZCRCQ: CRC next, send ZACK, frame continues nonstop
	 * ZCRCW: CRC next, send ZACK, frame ends, header follows
	 */

    transferstate = transferstart;

    ZFlowControl(1, info);

    if ((info->zrinitflags &(CANFDX | CANOVIO)) == (CANFDX | CANOVIO) &&
        (SendSample || SendAttn) && info->bufsize == 0) {
        if (info->windowsize == 0) {
            info->Streaming = Full;
        } else {
            info->Streaming = StrWindow;
        }
    }else if ((info->zrinitflags &(CANFDX | CANOVIO)) == (CANFDX | CANOVIO) && info->bufsize == 0) {
        info->Streaming = SlidingWindow;
    }else {
        info->Streaming = Segmented;
    }
    // get filenames to transfer
    zmodemlog("GotRinit[%s]\n", sname(info));

    if (AlwaysSinit || info->zsinitflags != 0 || info->attn != NULL) {
        SendZSInit(info);
    }


    itFile = strFileList.begin();
    QFileInfo fi(*itFile);

    FQ_TRACE("zmodem", 0) << "Number of files to be transferred: " << strFileList.count();

    char *filename = strdup(fi.absoluteFilePath().toLatin1());
    char *rfilename = strdup(fi.fileName().toLatin1());
    ZmodemTFile(filename, rfilename, 0, 0, 0, 0, strFileList.count(), fi.size(),  info);
    strFileList.erase(itFile);
    return ZmDone;
}


int FQTermZmodem::SendZSInit(ZModem *info)
{
    char tmp = '\0';
    int err;
    //char	*at = (info->attn != NULL) ? info->attn : "" ;
    char *at;
    if (info->attn != NULL) {
        at = info->attn;
    } else {
        at = &tmp;
    }
    uchar fbuf[4];

    /* TODO: zmodem8.doc states: "If the ZSINIT header specifies
	 * ESCCTL or ESC8, a HEX header is used, and the receiver
	 * activates the specified ESC modes before reading the following
	 * data subpacket." What does that mean?
	 */

    zmodemlog("SendZSInit[%s]\n", sname(info));

    info->state = TInit;
    fbuf[0] = fbuf[1] = fbuf[2] = 0;
    fbuf[3] = info->zsinitflags;
    if ((err = ZXmitHdr(ZSINIT, ZBIN, fbuf, info)) || (err = ZXmitData(ZBIN,
                                                                       strlen(at) + 1, ZCRCW, (uchar*)at, info))) {
        return err;
    }
    return 0;
}


int FQTermZmodem::SendFileCrc(ZModem *info)
{
    ulong crc;

    crc = FileCrc(info->filename);

    zmodemlog("SendFileCrc[%s]: %lx\n", sname(info), crc);

    return ZXmitHdrHex(ZCRC, ZEnc4(crc), info);
}

int FQTermZmodem::GotSendAck(ZModem *info)
{
    ulong offset;

    offset = ZDec4(info->hdrData + 1);

    if (offset > info->lastOffset) {
        info->lastOffset = offset;
    }

    zmodemlog("GotSendAck[%s]: %lx\n", sname(info), info->offset);

    return 0; /* DONT send more data, that will happen later anyway */
}

int FQTermZmodem::GotSendDoneAck(ZModem *info)
{
    ulong offset;

    offset = ZDec4(info->hdrData + 1);

    if (offset > info->lastOffset) {
        info->lastOffset = offset;
    }

    zmodemlog("GotSendDoneAck[%s]: %ld\n", sname(info), info->offset);

    info->state = SendEof;
    info->timeout = 60;
    return ZXmitHdrHex(ZEOF, ZEnc4(info->offset), info);
}

int FQTermZmodem::GotSendNak(ZModem *info)
{
    info->offset = info->zrposOffset;

    fseek(info->file, info->offset, 0);

    /* TODO: what if fseek fails?  Send EOF? */
    zmodemlog("GotSendNak[%s]: %ld\n", sname(info), info->offset);

    return SendMoreFileData(info);
}

int FQTermZmodem::GotSendWaitAck(ZModem *info)
{

    ulong offset;
    int err;

    offset = ZDec4(info->hdrData + 1);

    FQ_TRACE("zmodem", 10) << "last = " << info->lastOffset << ", now = " << offset;

    if (offset > info->lastOffset) {
        info->lastOffset = offset;
    }

    //receiver return -1 without setting this flag, kingson 00:07 14-07-04
    info->waitflag = 1;

    zmodemlog("GotSendWaitAck[%s]\n", sname(info), offset);

    if ((err = ZXmitHdr(ZDATA, ZBIN, info->hdrData + 1, info))) {
        return err;
    }
    return SendMoreFileData(info);
}

int FQTermZmodem::SkipFile(ZModem *info)
{
    zmodemlog("SkipFile[%s]\n", sname(info));
    ZStatus(FileEnd, 0, info->rfilename);
    fclose(info->file);

    // stupid SMTH doesn't send further command, kick
    // lets send files in the list
    info->state = TStart;
    return GotRinit(info);
}

int FQTermZmodem::GotSendPos(ZModem *info)
{
    ZStatus(DataErr, ++info->errCount, NULL);
    info->waitflag = 1; /* next pkt should wait, to resync */
    FQ_TRACE("zmodem", 9) << "GotSendPos, offset = " << info->offset;
    zmodemlog("GotSendPos[%s] %lx\n", sname(info), info->offset);
    return startFileData(info);
}

int FQTermZmodem::SendFileData(ZModem *info)
{
    info->waitflag = 0;
    return startFileData(info);
}

int FQTermZmodem::ResendEof(ZModem *info) {
    return ZXmitHdrHex(ZEOF, ZEnc4(info->offset), info);
}

int FQTermZmodem::OverAndOut(ZModem *info)
{
    zmodemlog("OverAndOut[%s]\n", sname(info));

    ZXmitStr((uchar*)"OO", 2, info);

    transferstate = transferstop; // transfer complete

    ZmodemReset(info); //Transfer complete, zmodem return to receive state

    return ZmDone;
}

int FQTermZmodem::startFileData(ZModem *info)
{
    int err;

    info->offset = info->lastOffset = info->zrposOffset = ZDec4(info->hdrData + 1);

    fseek(info->file, info->offset, 0);

    /* TODO: what if fseek fails?  Send EOF? */

    zmodemlog("startFileData[%s]: %lx\n", sname(info), info->offset);

    if ((err = ZXmitHdr(ZDATA, ZBIN, ZEnc4(info->offset), info))) {
        return err;
    }
    return SendMoreFileData(info);
}

uchar *FQTermZmodem::putHex(uchar *ptr, uchar c)
{
    *ptr++ = hexChars[(c >> 4) &0xf];
    *ptr++ = hexChars[c &0xf];
    return ptr;
}


int FQTermZmodem::ZmodemReset(ZModem *info)
{
    zmodemlog("\nZmodemReset\n");

    ZmodemRInit(info);

    return 0;
}


void FQTermZmodem::zmodemlog(const char *fmt, ...)
{
    // only for debug
#ifdef FQTERM_ZMODEM_DEBUG
    va_list ap;
    struct timeval tv;
    time_t t1;
    struct tm *tm = nullptr;
    static int do_ts = 1;

    zmodemlogfile = fopen("zmodem.log", "a");

    if (zmodemlogfile == NULL) {
        return ;
    }

    if (do_ts) {
        gettimeofday(&tv, NULL);
        t1 = time( NULL );
        tm = gmtime( &t1 );
        fprintf(zmodemlogfile, "%2d:%2d:%2d.%3ld: ", tm->tm_hour, tm->tm_min,
                tm->tm_sec, tv.tv_usec / 10000);
    }
    do_ts = strchr(fmt, '\n') != NULL;

    va_start(ap, fmt);
    vfprintf(zmodemlogfile, fmt, ap);
    va_end(ap);

    fclose(zmodemlogfile);
#endif

}

void FQTermZmodem::zmodemCancel()
{
    ZmodemAbort(&info);
}


}  // namespace FQTerm

